package main

import (
	"github.com/BurntSushi/toml"
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	"golang.org/x/crypto/bcrypt"
	"log"
	"net/http"
	"time"
	"fmt"
	"os/exec"
	"crypto/md5"
	"crypto/sha256"
	"crypto/sha1"
	"io"
	"os"
	"encoding/csv"
	"github.com/tarent/loginsrv/logging"
	"encoding/base64"
	"strings"
	"bytes"
	"crypto/subtle"
	"github.com/abbot/go-http-auth"
	"sync"
)


var Conf Toml
var Users Auth
var timeLayoutStr = "2006-01-02 15:04:05" //go中的时间格式化必须是这个时间
var db *gorm.DB
type ViewData struct {

}
func main() {
	if _, err := toml.DecodeFile("conf.toml", &Conf); err != nil {
		log.Println(err)
		return
	}

	router := gin.Default()


	router.LoadHTMLGlob("template/*")
	router.StaticFS("/resources", http.Dir("resources"))
	router.StaticFile("/favicon.ico", "resources/favicon.ico")
	//Client = GetRedisClient()
	router.GET("/", HomePage)
	router.POST("/modify", ModifyPwd)

	port := Conf.Base.LocalPort
	strPort := fmt.Sprintf(":%d", port)
	router.Run(strPort)

}

type UserInfo struct {
	Username string `form:"Username" json:"Username" binding:"required"`
	OldPasswd string `form:"OldPasswd" json:"OldPasswd" binding:"required"`
	NewPasswd string `form:"NewPasswd" json:"NewPasswd" binding:"required"`
	ReNewPasswd string `form:"ReNewPasswd" json:"ReNewPasswd" binding:"required"`
}
// Auth is the htpassword authenticater
type Auth struct {
	filenames  []File
	userHash   map[string]string
	muUserHash sync.RWMutex
}
type info struct {
	Pwd [] string `form:"pwd[]"`
}
// File is a struct to serve an individual modTime
type File struct {
	name string
	// Used in func reloadIfChanged to reload htpasswd file if it changed
	modTime time.Time
}
// NewAuth creates an htpassword authenticater
func NewAuth(filenames []string) (*Auth, error) {
	var htpasswdFiles []File
	for _, file := range filenames {
		htpasswdFiles = append(htpasswdFiles, File{name: file})
	}

	a := &Auth{
		filenames: htpasswdFiles,
	}
	return a, a.parse()
}
func (a *Auth) parse() error{

	tmpUserHash := map[string]string{}
	tmpFilenames := a.filenames

	for i, filename := range a.filenames {
		r, err := os.Open(filename.name)
		if err != nil {
			return err
		}
		defer r.Close()

		fileInfo, err := os.Stat(filename.name)
		if err != nil {
			return err
		}
		tmpFilenames[i].modTime = fileInfo.ModTime()

		cr := csv.NewReader(r)
		cr.Comma = ':'
		cr.Comment = '#'
		cr.TrimLeadingSpace = true

		for {
			record, err := cr.Read()
			if err == io.EOF {
				break
			}
			if err != nil {
				return err
			}
			if len(record) != 2 {
				return fmt.Errorf("password file in wrong format (%v)", filename)
			}

			if _, exist := tmpUserHash[record[0]]; exist {
				logging.Logger.Warnf("Found duplicate entry for user: (%v)", record[0])
			}
			tmpUserHash[record[0]] = record[1]
		}
	}
	a.muUserHash.Lock()
	a.userHash = tmpUserHash
	a.filenames = tmpFilenames
	a.muUserHash.Unlock()

	return nil
}
// Reload htpasswd file if it changed during current run
func reloadIfChanged(a *Auth) {

	for _, file := range a.filenames {
		fileInfo, err := os.Stat(file.name)
		if err != nil {
			//On error, retain current file
			break
		}
		currentmodTime := fileInfo.ModTime()
		if currentmodTime != file.modTime {
			a.parse()
			return
		}
	}

}
// Authenticate the user
func (a *Auth) Authenticate(username, password string) (bool, error) {
	reloadIfChanged(a)
	a.muUserHash.RLock()
	defer a.muUserHash.RUnlock()
	if hash, exist := a.userHash[username]; exist {
		h := []byte(hash)
		p := []byte(password)
		if strings.HasPrefix(hash, "$2y$") || strings.HasPrefix(hash, "$2b$") || strings.HasPrefix(hash, "$2a$") {
			matchErr := bcrypt.CompareHashAndPassword(h, p)
			return (matchErr == nil), nil
		}
		if strings.HasPrefix(hash, "{SHA}") {
			return compareSha(h, p), nil
		}
		if strings.HasPrefix(hash, "$apr1$") {
			return compareMD5(h, p), nil
		}
		return false, fmt.Errorf("unknown algorithm for user %q", username)
	}
	return false, nil
}
func ModifyPwd(c *gin.Context)  {

	//var moped info
	//c.Bind(&moped)
	//c.JSON(200, gin.H{"pwd": "修改成功"})
	var user UserInfo
	err := c.Bind(&user)
	if err != nil {
		log.Fatal(err)
	}


	a,errAuth := NewAuth([]string{Conf.Pwd.PwdFile})
	if errAuth != nil {
		log.Fatal(errAuth)
	}
	authenticated, err :=a.Authenticate(user.Username,user.OldPasswd)
	if authenticated == true{
		fmt.Printf("Auth Success:%s\n", authenticated)
	}else{
		//fmt.Printf("Auth Fail:%s\n", authenticated)
		c.JSON(http.StatusOK, gin.H{
			"State":"用户名或密码错误，用户名为个人姓名全拼小写，如忘记密码请联系管理员！！！"})
		return
	}



	if user.NewPasswd == user.ReNewPasswd{
		cmd := exec.Command( Conf.Pwd.PwdExe, "-b", Conf.Pwd.PwdFile, user.Username, user.ReNewPasswd)
		//cmd.Run()
		buf,err:= cmd.Output()
		if err != nil{
			fmt.Printf(err.Error())
		}
		fmt.Printf("用户: %s 修改密码 :%s: %s : %s \n",user.Username, string(buf), cmd.Stdout, cmd.Stderr)
		c.JSON(http.StatusOK, gin.H{
			"State":"密码修改成功"})
	}else{
		c.JSON(http.StatusOK, gin.H{
			"State":"两次输入密码不一致！"})
	}
}

func compareMD5(hashedPassword, password []byte) bool {
	parts := bytes.SplitN(hashedPassword, []byte("$"), 4)
	if len(parts) != 4 {
		return false
	}
	magic := []byte("$" + string(parts[1]) + "$")
	salt := parts[2]
	return 1 == subtle.ConstantTimeCompare(hashedPassword, auth.MD5Crypt(password, salt, magic))
}
func compareSha(hashedPassword, password []byte) bool {
	d := sha1.New()
	d.Write(password)
	return 1 == subtle.ConstantTimeCompare(hashedPassword[5:], []byte(base64.StdEncoding.EncodeToString(d.Sum(nil))))
}

// Base64EncodeString 编码
func Base64EncodeString(str string) string {
	return base64.StdEncoding.EncodeToString([]byte(str))
}

// Base64DecodeString 解码
func Base64DecodeString(str string) (string, []byte) {
	resBytes, _ := base64.StdEncoding.DecodeString(str)
	return string(resBytes), resBytes
}
func GetMd5FromString(data string) string {
	h := md5.New()
	io.WriteString(h, data)
	sum := fmt.Sprintf("%x", h.Sum(nil))
	return sum
}
func GetSHA1FromString(data string) string {
	h := sha1.New()
	io.WriteString(h, data)
	sum := fmt.Sprintf("%x", h.Sum(nil))
	return sum
}
func GetSHA256FromString(data string) string {
	h := sha256.New()
	io.WriteString(h, data)
	sum := fmt.Sprintf("%x", data)
	return sum
}
func HomePage(c *gin.Context) {

	var arr []ViewData
	c.HTML(http.StatusOK, "modifypwd.html", gin.H{
		"datas": arr,
	})
}
